﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Management;

namespace System.Peppers.Wmi
{
    /// <summary>
    /// Class wrapping a Wmi object, making it easier to work with
    /// </summary>
    public class WmiWrapper : IDisposable
    {
        #region Static
        private static Dictionary<string, ManagementClass> _staticInstances = new Dictionary<string, ManagementClass>();

        /// <summary>
        /// Get's a WMI class instance for use by static methods, caching and reusing them
        /// </summary>
        /// <param name="wmiClass">Class to retrieve/or create</param>
        /// <returns>WMI class instance</returns>
        internal static ManagementClass GetStaticInstance(string wmiClass)
        {
            lock (_staticInstances)
            {
                ManagementClass ret = null;
                if (_staticInstances.TryGetValue(wmiClass, out ret))
                {
                    return ret;
                }

                ret = new ManagementClass(wmiClass);
                _staticInstances[wmiClass] = ret;
                return ret;
            }
        }

        internal static ManagementClass GetStaticInstance(string wmiClass, WmiRemoteParameters remoteParams)
        {
            const string DEFAULT_SCOPE = @"\\{0}\root\cimv2";
            return new ManagementClass(
                new ManagementScope(string.Format(DEFAULT_SCOPE, remoteParams.Hostname),
                    new ConnectionOptions
                    {
                        Username = remoteParams.Username,
                        Password = remoteParams.Password,
                    }),
                new ManagementPath(wmiClass),
                remoteParams == null ? null : new ObjectGetOptions { Timeout = remoteParams.Timeout.Value });
        }

        internal static IEnumerable<ManagementObject> GetInstances(string wmiClass)
        {
            ManagementClass @class = GetStaticInstance(wmiClass);
            foreach (ManagementObject item in @class.GetInstances())
            {
                yield return item;
            }
        }

        internal static IEnumerable<ManagementObject> GetInstances(string wmiClass, WmiRemoteParameters remoteParams)
        {
            using (ManagementClass @class = GetStaticInstance(wmiClass, remoteParams))
            {
                foreach (ManagementObject item in @class.GetInstances())
                {
                    yield return item;
                }
            }
        }
        #endregion

        private ManagementObject _wmiObject = null;

        internal WmiWrapper(ManagementObject wmiObject)
        {
            _wmiObject = wmiObject;
        }

        internal ManagementObject WmiInstance
        {
            get { return _wmiObject; }
        }

        /// <summary>
        /// Finalizer
        /// </summary>
        ~WmiWrapper()
        {
            Dispose();
        }

        /// <summary>
        /// Refreshs properties on the object
        /// </summary>
        public void Refresh()
        {
            _wmiObject.Get();
        }

        /// <summary>
        /// Saves any modified properties on teh object
        /// </summary>
        public void Save()
        {
            _wmiObject.Put();
        }

        /// <summary>
        /// Gets a property from the Wmi base object
        /// </summary>
        /// <param name="property">String of the property name</param>
        /// <returns>Value of property</returns>
        protected internal object this[string property]
        {
            get { return _wmiObject[property]; }
            set { _wmiObject[property] = value; }
        }

        /// <summary>
        /// Executes a method on the Wmi base object
        /// </summary>
        /// <param name="method">Name of method</param>
        /// <param name="callback">Callback that allows input parameters to be set</param>
        /// <returns>Output parameters</returns>
        protected internal ManagementBaseObject ExecuteMethod(string method, Action<ManagementBaseObject> callback)
        {
            using (ManagementBaseObject inParams = _wmiObject.GetMethodParameters(method))
            {
                if (callback != null)
                {
                    callback(inParams);
                }

                return _wmiObject.InvokeMethod(method, inParams, null);
            }
        }

        /// <summary>
        /// Executes a method on the Wmi base object that returns a uint
        /// </summary>
        /// <param name="method">Name of method</param>
        /// <param name="callback">Callback that allows input parameters to be set</param>
        /// <returns>Result as a uint</returns>
        protected internal uint ExecuteMethodWithResult(string method, Action<ManagementBaseObject> callback)
        {
            using (ManagementBaseObject outParam = ExecuteMethod(method, callback))
            {
                return Convert.ToUInt32(outParam[WmiClasses.ReturnValue]);
            }
        }

        /// <summary>
        /// Executes a method on the Wmi base object that returns a ManagementStatus
        /// </summary>
        /// <param name="method">Name of method</param>
        /// <param name="callback">Callback that allows input parameters to be set</param>
        /// <returns>Result as a uint</returns>
        protected internal ManagementStatus ExecuteStatusMethod(string method, Action<ManagementBaseObject> callback)
        {
            using (ManagementBaseObject outParam = ExecuteMethod(method, callback))
            {
                return (ManagementStatus)Convert.ToUInt32(outParam[WmiClasses.ReturnValue]);
            }
        }

        /// <summary>
        /// Disposes underlying Wmi Object
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);

            if (_wmiObject != null)
            {
                _wmiObject.Dispose();
                _wmiObject = null;
            }
        }
    }
}
